/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.talkback.labeling;

import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.android.talkback.R;
import com.android.utils.LogUtils;
import com.android.utils.labeling.CustomLabelManager;
import com.android.utils.labeling.Label;
import com.google.android.marvin.talkback.TalkBackService;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CustomLabelMigrationManager {

    private static final String JSON_LABELS_ARRAY = "labels_array";
    private static final String JSON_LABEL_PACKAGE_NAME = "package_name";
    private static final String JSON_LABEL_PACKAGE_SIGNATURE = "package_signature";
    private static final String JSON_LABEL_VIEW_NAME = "view_name";
    private static final String JSON_LABEL_TEXT = "label_text";
    private static final String JSON_LABEL_LOCALE = "locale";
    private static final String JSON_PACKAGE_VERSION = "package_version";
    private static final String JSON_TIMESTAMP = "timestamp";

    public interface OnLabelMigrationCallback {
        public void onLabelsExported(File file);
        public void onLabelImported(int updateCount);
        public void onFail();
    }

    public static class SimpleLabelMigrationCallback implements OnLabelMigrationCallback {
        @Override
        public void onLabelsExported(File file) { }
        @Override
        public void onLabelImported(int updateCount) { }
        @Override
        public void onFail() { }
    }

    private ExecutorService mExecutor;
    private Handler mHandler;
    private CustomLabelManager mManager;
    private Context mContext;

    public CustomLabelMigrationManager(Context context) {
        mExecutor = Executors.newSingleThreadExecutor();
        mHandler = new Handler();
        TalkBackService service = TalkBackService.getInstance();
        if (service != null) {
            mManager = service.getLabelManager();
        } else {
            mManager = new CustomLabelManager(context);
        }
        mContext = context;
    }

    public void exportLabels(final OnLabelMigrationCallback callback) {
        mManager.getLabelsFromDatabase(new LabelsFetchRequest.OnLabelsFetchedListener() {
            @Override
            public void onLabelsFetched(List<Label> results) {
                if (results != null && results.size() > 0) {
                    createLabelFileAsync(results, callback);
                } else {
                    Toast.makeText(mContext, R.string.label_export_empty, Toast.LENGTH_SHORT)
                            .show();
                }
            }
        });
    }

    private void createLabelFileAsync(final List<Label> labels,
                                      final OnLabelMigrationCallback callback) {
        mExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    String jsonText = generateJsonText(labels);
                    if (jsonText != null) {
                        final File file = getFilePath();
                        writeToFile(jsonText, file);
                        if (callback != null) {
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    callback.onLabelsExported(file);
                                }
                            });
                        }
                    } else {
                        notifyFailure(callback);
                    }
                } catch (Exception e) {
                    notifyFailure(callback);
                    LogUtils.log(Log.ERROR, "Failed to export labels");
                }
            }
        });
    }

    private void notifyFailure(final OnLabelMigrationCallback callback) {
        if (callback != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    callback.onFail();
                }
            });
        }
    }

    // reduced visibility for tests
    String generateJsonText(List<Label> labels) throws JSONException {
        JSONObject root = new JSONObject();
        JSONArray labelsArray = new JSONArray();
        for (Label label : labels) {
            if (label != null) {
                JSONObject labelObject = new JSONObject();
                labelObject.put(JSON_LABEL_PACKAGE_NAME, label.getPackageName());
                labelObject.put(JSON_LABEL_PACKAGE_SIGNATURE, label.getPackageSignature());
                labelObject.put(JSON_LABEL_VIEW_NAME, label.getViewName());
                labelObject.put(JSON_LABEL_TEXT, label.getText());
                labelObject.put(JSON_LABEL_LOCALE, label.getLocale());
                labelObject.put(JSON_PACKAGE_VERSION, label.getPackageVersion());
                labelObject.put(JSON_TIMESTAMP, label.getTimestamp());
                labelsArray.put(labelObject);
            }
        }

        root.put(JSON_LABELS_ARRAY, labelsArray);
        return root.toString();
    }

    private File getFilePath() throws IOException {
        File outputDir = mContext.getExternalCacheDir();
        String fileName = String.format("Talkback_custom_labels_%s.tbl",
                new SimpleDateFormat("MMddyyyy").format(new Date()));
        return new File(outputDir, fileName);
    }

    private void writeToFile(String content, File file) throws IOException {
        Writer writer = new BufferedWriter(new FileWriter(file));
        writer.write(content);
        writer.close();
    }

    public void importLabels(Uri contentUri, boolean overrideExistingLabels,
                             OnLabelMigrationCallback callback) {
        try {
            String text = readText(contentUri);
            if (text == null) {
                return;
            }

            List<Label> labels = parseLabels(text);
            if (labels.size() == 0) {
                return;
            }

            mManager.importLabels(labels, overrideExistingLabels, callback);
        } catch (Exception e) {
            notifyFailure(callback);
            LogUtils.log(Log.ERROR, "failed to import labels");
        }
    }

    private String readText(Uri contentUri) throws IOException {
        StringBuilder text = new StringBuilder();
        InputStream is = mContext.getContentResolver().openInputStream(contentUri);
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line;

        while ((line = br.readLine()) != null) {
            text.append(line);
            text.append('\n');
        }
        br.close();
        return text.toString();
    }

    // reduced visibility for tests
    @NonNull List<Label> parseLabels(String jsonText) throws JSONException {
        JSONObject root = new JSONObject(jsonText);
        JSONArray labelsArray = root.getJSONArray(JSON_LABELS_ARRAY);
        if (labelsArray == null) {
            return Collections.emptyList();
        }

        int count = labelsArray.length();
        List<Label> result = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            JSONObject labelObject = labelsArray.getJSONObject(i);
            String packageName = labelObject.getString(JSON_LABEL_PACKAGE_NAME);
            if (TextUtils.isEmpty(packageName)) {
                continue;
            }
            String packageSignature = labelObject.getString(JSON_LABEL_PACKAGE_SIGNATURE);
            String viewName = labelObject.getString(JSON_LABEL_VIEW_NAME);
            if (TextUtils.isEmpty(viewName)) {
                continue;
            }
            String labelText = labelObject.getString(JSON_LABEL_TEXT);
            if (TextUtils.isEmpty(labelText)) {
                continue;
            }
            String locale = labelObject.getString(JSON_LABEL_LOCALE);
            int packageVersion = labelObject.getInt(JSON_PACKAGE_VERSION);
            long timestamp = labelObject.getLong(JSON_TIMESTAMP);
            Label label = new Label(packageName, packageSignature, viewName, labelText, locale,
                    packageVersion, "", timestamp);
            result.add(label);
        }

        return result;
    }
}
